package main

import (
	"bytes"
	"flag"
	"fmt"
	"go/format"
	"io/ioutil"
	"os"
	"path/filepath"
	"strconv"
	"strings"
)

var gE = flag.Bool("E", false, "Generate code before fmt for troubleshooting")
var gAddServant = flag.Bool("add-servant", true, "Generate AddServant function")

//GenGo record go code information.
type GenGo struct {
	code     bytes.Buffer
	vc       int      // var count. Used to generate unique variable names
	I        []string // imports with path
	path     string
	prefix   string
	tarsPath string
	p        *Parse
}

//Initial capitalization
func upperFirstLatter(s string) string {
	if len(s) == 0 {
		return ""
	}
	if len(s) == 1 {
		return strings.ToUpper(string(s[0]))
	}
	return strings.ToUpper(string(s[0])) + s[1:]
}

// === rename area ===
// 0. rename module
func (p *Parse) rename() {
	p.OriginModule = p.Module
	p.Module = upperFirstLatter(p.Module)
}

// 1. struct rename
// struct TName { 1 require Mb type}
func (st *StructInfo) rename() {
	st.TName = upperFirstLatter(st.TName)
	for i := range st.Mb {
		st.Mb[i].KeyStr = st.Mb[i].Key
		// Member variables force initial capitalization.
		st.Mb[i].Key = upperFirstLatter(st.Mb[i].Key)
	}
}

// 1. interface rename
// interface TName { Fun }
func (itf *InterfaceInfo) rename() {
	itf.TName = upperFirstLatter(itf.TName)
	for i := range itf.Fun {
		itf.Fun[i].rename()
	}
}

func (en *EnumInfo) rename() {
	en.TName = upperFirstLatter(en.TName)
	for i := range en.Mb {
		en.Mb[i].Key = upperFirstLatter(en.Mb[i].Key)
	}
}

func (cst *ConstInfo) rename() {
	cst.Key = upperFirstLatter(cst.Key)
}

// 2. func rename
// type Fun (arg ArgType), in case keyword and name conflicts,argname need to capitalize.
// Fun (type int32)
func (fun *FunInfo) rename() {
	fun.NameStr = fun.Name
	fun.Name = upperFirstLatter(fun.Name)
	for i := range fun.Args {
		fun.Args[i].Name = upperFirstLatter(fun.Args[i].Name)
	}
}

// 3. genType rename all Type

// === rename end ===

func (gen *GenGo) genErr(err string) {
	panic(err)
}

func (gen *GenGo) saveToSourceFile(filename string) {
	var beauty []byte
	var err error
	prefix := gen.prefix

	if !*gE {
		beauty, err = format.Source(gen.code.Bytes())
		if err != nil {
			gen.genErr("go fmt fail. " + err.Error())
		}
	} else {
		beauty = gen.code.Bytes()
	}

	if filename == "stdout" {
		fmt.Println(string(beauty))
	} else {
		err = os.MkdirAll(prefix+gen.p.Module, 0766)

		if err != nil {
			gen.genErr(err.Error())
		}
		err = ioutil.WriteFile(prefix+gen.p.Module+"/"+filename, beauty, 0666)

		if err != nil {
			gen.genErr(err.Error())
		}
	}
}

func (gen *GenGo) genHead() {
	gen.code.WriteString(`//Package ` + gen.p.Module + ` comment
// This file war generated by tars2go ` + VERSION + `
// Generated from ` + filepath.Base(gen.path) + `
`)
}

func (gen *GenGo) genPackage() {
	gen.code.WriteString("package " + gen.p.Module + "\n\n")
}

func (gen *GenGo) genImport(module string) {
	for _, p := range gen.I {
		if strings.HasSuffix(p, "/"+module) {
			gen.code.WriteString(`"` + p + `"` + "\n")
			return
		}
	}
	if *gAddServant == true {
		gen.code.WriteString(`"` + upperFirstLatter(module) + `"` + "\n")
	} else {
		gen.code.WriteString(`"` + module + `"` + "\n")
	}
}

func (gen *GenGo) genStructPackage(st *StructInfo) {
	gen.code.WriteString("package " + gen.p.Module + "\n\n")
	gen.code.WriteString(`
import (
"fmt"
`)
	//"tars/protocol/codec"
	gen.code.WriteString("\"" + gen.tarsPath + "/protocol/codec\"\n")
	for k := range st.DependModule {
		gen.genImport(k)
	}
	gen.code.WriteString(")" + "\n")

}

func (gen *GenGo) genIFPackage(itf *InterfaceInfo) {
	gen.code.WriteString("package " + gen.p.Module + "\n\n")
	gen.code.WriteString(`
import (
"fmt"
"context"
`)
	gen.code.WriteString("\"" + gen.tarsPath + "/protocol/res/requestf\"\n")
	gen.code.WriteString("m \"" + gen.tarsPath + "/model\"\n")
	gen.code.WriteString("\"" + gen.tarsPath + "/protocol/codec\"\n")
	gen.code.WriteString("\"" + gen.tarsPath + "/util/tools\"\n")
	gen.code.WriteString("\"" + gen.tarsPath + "/util/current\"\n")

	if *gAddServant {
		gen.code.WriteString("\"" + gen.tarsPath + "\"\n")
	}
	for k := range itf.DependModule {
		gen.genImport(k)
	}
	gen.code.WriteString(")" + "\n")

}

func (gen *GenGo) genType(ty *VarType) string {
	ret := ""
	switch ty.Type {
	case tkTBool:
		ret = "bool"
	case tkTInt:
		if ty.Unsigned {
			ret = "uint32"
		} else {
			ret = "int32"
		}
	case tkTShort:
		if ty.Unsigned {
			ret = "uint16"
		} else {
			ret = "int16"
		}
	case tkTByte:
		if ty.Unsigned {
			ret = "uint8"
		} else {
			ret = "int8"
		}
	case tkTLong:
		if ty.Unsigned {
			ret = "uint64"
		} else {
			ret = "int64"
		}
	case tkTFloat:
		ret = "float32"
	case tkTDouble:
		ret = "float64"
	case tkTString:
		ret = "string"
	case tkTVector:
		ret = "[]" + gen.genType(ty.TypeK)
	case tkTMap:
		ret = "map[" + gen.genType(ty.TypeK) + "]" + gen.genType(ty.TypeV)
	case tkName:
		//Actrually ret is useless here
		//ret = strings.Replace(ty.TypeSt, "::", ".", -1)
		vec := strings.Split(ty.TypeSt, "::")
		for i := range vec {
			if *gAddServant == true {
				vec[i] = upperFirstLatter(vec[i])
			} else {
				if i == (len(vec) - 1) {
					vec[i] = upperFirstLatter(vec[i])
				}
			}
		}
		ret = strings.Join(vec, ".")
	default:
		gen.genErr("Unknow Type " + TokenMap[ty.Type])
	}
	return ret
}

func (gen *GenGo) genStructDefine(st *StructInfo) {
	c := &gen.code
	c.WriteString("//" + st.TName + " strcut implement\n")
	c.WriteString("type " + st.TName + " struct {\n")

	for _, v := range st.Mb {
		c.WriteString("\t" + v.Key + " " + gen.genType(v.Type) + "`json:\"" + v.KeyStr + "\"`\n")
	}
	c.WriteString("}\n")
}

func (gen *GenGo) genFunResetDefault(st *StructInfo) {
	c := &gen.code

	c.WriteString("func (st *" + st.TName + ") resetDefault() {\n")

	for _, v := range st.Mb {
		if v.Default == "" {
			continue
		}
		c.WriteString("st." + v.Key + " = " + v.Default + "\n")
	}
	c.WriteString("}\n")
}

func errString(hasRet bool) string {
	var retStr string
	if hasRet {
		retStr = "return ret, err"
	} else {
		retStr = "return err"
	}
	return `if err != nil {
  ` + retStr + `
  }`
}

func (gen *GenGo) genWriteSimpleList(mb *StructMember, prefix string, hasRet bool) {
	c := &gen.code
	tag := strconv.Itoa(int(mb.Tag))
	unsign := ""
	if mb.Type.TypeK.Unsigned {
		unsign = "u"
	}
	errStr := errString(hasRet)
	c.WriteString(`
err = _os.WriteHead(codec.SIMPLE_LIST, ` + tag + `)
` + errStr + `
err = _os.WriteHead(codec.BYTE, 0)
` + errStr + `
err = _os.Write_int32(int32(len(` + prefix + mb.Key + `)), 0)
` + errStr + `
err = _os.Write_slice_` + unsign + `int8(` + prefix + mb.Key + `)
` + errStr + `
`)
}

func (gen *GenGo) genWriteVector(mb *StructMember, prefix string, hasRet bool) {
	c := &gen.code

	// SIMPLE_LIST
	if mb.Type.TypeK.Type == tkTByte && !mb.Type.TypeK.Unsigned {
		gen.genWriteSimpleList(mb, prefix, hasRet)
		return
	}
	errStr := errString(hasRet)

	// LIST
	tag := strconv.Itoa(int(mb.Tag))
	c.WriteString(`
err = _os.WriteHead(codec.LIST, ` + tag + `)
` + errStr + `
err = _os.Write_int32(int32(len(` + prefix + mb.Key + `)), 0)
` + errStr + `
for _, v := range ` + prefix + mb.Key + ` {
`)
	// for _, v := range can nesting for _, v := range，does not conflict, support multidimensional arrays

	dummy := &StructMember{}
	dummy.Type = mb.Type.TypeK
	dummy.Key = "v"
	gen.genWriteVar(dummy, "", hasRet)

	c.WriteString("}\n")
}

func (gen *GenGo) genWriteStruct(mb *StructMember, prefix string, hasRet bool) {
	c := &gen.code
	tag := strconv.Itoa(int(mb.Tag))
	c.WriteString(`
err = ` + prefix + mb.Key + `.WriteBlock(_os, ` + tag + `)
` + errString(hasRet) + `
`)
}

func (gen *GenGo) genWriteMap(mb *StructMember, prefix string, hasRet bool) {
	c := &gen.code
	tag := strconv.Itoa(int(mb.Tag))
	vc := strconv.Itoa(gen.vc)
	gen.vc++
	errStr := errString(hasRet)
	c.WriteString(`
err = _os.WriteHead(codec.MAP, ` + tag + `)
` + errStr + `
err = _os.Write_int32(int32(len(` + prefix + mb.Key + `)), 0)
` + errStr + `
for k` + vc + `, v` + vc + ` := range ` + prefix + mb.Key + ` {
`)
	// for _, v := range can nesting for _, v := range，does not conflict, support multidimensional arrays

	dummy := &StructMember{}
	dummy.Type = mb.Type.TypeK
	dummy.Key = "k" + vc
	gen.genWriteVar(dummy, "", hasRet)

	dummy = &StructMember{}
	dummy.Type = mb.Type.TypeV
	dummy.Key = "v" + vc
	dummy.Tag = 1
	gen.genWriteVar(dummy, "", hasRet)

	c.WriteString("}\n")
}

func (gen *GenGo) genWriteVar(v *StructMember, prefix string, hasRet bool) {
	c := &gen.code

	switch v.Type.Type {
	case tkTVector:
		gen.genWriteVector(v, prefix, hasRet)
	case tkTMap:
		gen.genWriteMap(v, prefix, hasRet)
	case tkName:
		if v.Type.CType == tkEnum {
			// tkEnum enumeration processing
			tag := strconv.Itoa(int(v.Tag))
			c.WriteString(`
err = _os.Write_int32(int32(` + prefix + v.Key + `),` + tag + `)
` + errString(hasRet) + `
`)
		} else {
			gen.genWriteStruct(v, prefix, hasRet)
		}
	default:
		tag := strconv.Itoa(int(v.Tag))
		c.WriteString(`
err = _os.Write_` + gen.genType(v.Type) + `(` + prefix + v.Key + `, ` + tag + `)
` + errString(hasRet) + `
`)
	}
}

func (gen *GenGo) genFunWriteBlock(st *StructInfo) {
	c := &gen.code

	// WriteBlock function head
	c.WriteString(`//WriteBlock encode struct
func (st *` + st.TName + `) WriteBlock(_os *codec.Buffer, tag byte) error {
	var err error
	err = _os.WriteHead(codec.STRUCT_BEGIN, tag)
	if err != nil {
		return err
	}

  st.WriteTo(_os)

	err = _os.WriteHead(codec.STRUCT_END, 0)
	if err != nil {
		return err
	}
	return nil
}
`)
}

func (gen *GenGo) genFunWriteTo(st *StructInfo) {
	c := &gen.code

	c.WriteString(`//WriteTo encode struct to buffer
func (st *` + st.TName + `) WriteTo(_os *codec.Buffer) error {
	var err error
`)
	for _, v := range st.Mb {
		gen.genWriteVar(&v, "st.", false)
	}

	c.WriteString(`
	return nil
}
`)
}

func (gen *GenGo) genReadSimpleList(mb *StructMember, prefix string, hasRet bool) {
	c := &gen.code
	unsign := ""
	if mb.Type.TypeK.Unsigned {
		unsign = "u"
	}
	errStr := errString(hasRet)

	c.WriteString(`
err, _ = _is.SkipTo(codec.BYTE, 0, true)
` + errStr + `
err = _is.Read_int32(&length, 0, true)
` + errStr + `
err = _is.Read_slice_` + unsign + `int8(&` + prefix + mb.Key + `, length, true)
` + errStr + `
`)
}

func genForHead(vc string) string {
	i := `i` + vc
	e := `e` + vc
	return ` for ` + i + `,` + e + ` := int32(0),length;` + i + `<` + e + `;` + i + `++ `
}

func (gen *GenGo) genReadVector(mb *StructMember, prefix string, hasRet bool) {
	c := &gen.code
	errStr := errString(hasRet)

	// LIST
	tag := strconv.Itoa(int(mb.Tag))
	vc := strconv.Itoa(gen.vc)
	gen.vc++
	require := "false"
	if mb.Require {
		require = "true"
	}

	if require == "false" {
		c.WriteString(`
		err, have, ty = _is.SkipToNoCheck(` + tag + `,` + require + `)
		` + errStr + `
		`)
		c.WriteString("if have {")
	} else {
		c.WriteString(`
		err, _, ty = _is.SkipToNoCheck(` + tag + `,` + require + `)
		` + errStr + `
		`)
	}

	c.WriteString(`
if ty == codec.LIST {
	err = _is.Read_int32(&length, 0, true)
  ` + errStr + `
  ` + prefix + mb.Key + ` = make(` + gen.genType(mb.Type) + `, length, length)
  ` + genForHead(vc) + `{
`)

	dummy := &StructMember{}
	dummy.Type = mb.Type.TypeK
	dummy.Key = mb.Key + "[i" + vc + "]"
	gen.genReadVar(dummy, prefix, hasRet)

	c.WriteString(`}
} else if ty == codec.SIMPLE_LIST {
`)
	if mb.Type.TypeK.Type == tkTByte {
		gen.genReadSimpleList(mb, prefix, hasRet)
	} else {
		c.WriteString(`err = fmt.Errorf("not support simple_list type")
    ` + errStr)
	}
	c.WriteString(`
} else {
  err = fmt.Errorf("require vector, but not")
  ` + errStr + `
}
`)

	if require == "false" {
		c.WriteString("}\n")
	}
}

func (gen *GenGo) genReadStruct(mb *StructMember, prefix string, hasRet bool) {
	c := &gen.code
	tag := strconv.Itoa(int(mb.Tag))
	require := "false"
	if mb.Require {
		require = "true"
	}
	c.WriteString(`
err = ` + prefix + mb.Key + `.ReadBlock(_is, ` + tag + `, ` + require + `)
` + errString(hasRet) + `
`)
}

func (gen *GenGo) genReadMap(mb *StructMember, prefix string, hasRet bool) {
	c := &gen.code
	tag := strconv.Itoa(int(mb.Tag))
	errStr := errString(hasRet)
	vc := strconv.Itoa(gen.vc)
	gen.vc++
	require := "false"
	if mb.Require {
		require = "true"
	}
	c.WriteString(`
err, have = _is.SkipTo(codec.MAP, ` + tag + `, ` + require + `)
` + errStr + `
`)
	if require == "false" {
		c.WriteString("if have {")
	}
	c.WriteString(`
err = _is.Read_int32(&length, 0, true)
` + errStr + `
` + prefix + mb.Key + ` = make(` + gen.genType(mb.Type) + `)
` + genForHead(vc) + `{
	var k` + vc + ` ` + gen.genType(mb.Type.TypeK) + `
	var v` + vc + ` ` + gen.genType(mb.Type.TypeV) + `
`)

	dummy := &StructMember{}
	dummy.Type = mb.Type.TypeK
	dummy.Key = "k" + vc
	gen.genReadVar(dummy, "", hasRet)

	dummy = &StructMember{}
	dummy.Type = mb.Type.TypeV
	dummy.Key = "v" + vc
	dummy.Tag = 1
	gen.genReadVar(dummy, "", hasRet)

	c.WriteString(`
	` + prefix + mb.Key + `[k` + vc + `] = v` + vc + `
}
`)
	if require == "false" {
		c.WriteString("}\n")
	}
}

func (gen *GenGo) genReadVar(v *StructMember, prefix string, hasRet bool) {
	c := &gen.code

	switch v.Type.Type {
	case tkTVector:
		gen.genReadVector(v, prefix, hasRet)
	case tkTMap:
		gen.genReadMap(v, prefix, hasRet)
	case tkName:
		if v.Type.CType == tkEnum {
			tag := strconv.Itoa(int(v.Tag))
			require := "false"
			if v.Require {
				require = "true"
			}
			c.WriteString(`
err = _is.Read_int32((*int32)(&` + prefix + v.Key + `),` + tag + `, ` + require + `)
` + errString(hasRet) + `
`)
		} else {
			gen.genReadStruct(v, prefix, hasRet)
		}
	default:
		tag := strconv.Itoa(int(v.Tag))
		require := "false"
		if v.Require {
			require = "true"
		}
		c.WriteString(`
err = _is.Read_` + gen.genType(v.Type) + `(&` + prefix + v.Key + `, ` + tag + `, ` + require + `)
` + errString(hasRet) + `
`)
	}
}

func (gen *GenGo) genFunReadFrom(st *StructInfo) {
	c := &gen.code

	c.WriteString(`//ReadFrom reads  from _is and put into struct.
func (st *` + st.TName + `) ReadFrom(_is *codec.Reader) error {
	var err error
	var length int32
	var have bool
	var ty byte
	st.resetDefault()

`)

	for _, v := range st.Mb {
		gen.genReadVar(&v, "st.", false)
	}

	c.WriteString(`
	_ = length
	_ = have
	_ = ty
	return nil
}
`)
}

func (gen *GenGo) genFunReadBlock(st *StructInfo) {
	c := &gen.code

	c.WriteString(`//ReadBlock reads struct from the given tag , require or optional.
func (st *` + st.TName + `) ReadBlock(_is *codec.Reader, tag byte, require bool) error {
	var err error
	var have bool
	st.resetDefault()

	err, have = _is.SkipTo(codec.STRUCT_BEGIN, tag, require)
	if err != nil {
		return err
	}
  if !have {
    if require {
      return fmt.Errorf("require ` + st.TName + `, but not exist. tag %d", tag)    
    } 
      return nil
    
  }

  st.ReadFrom(_is)

	err = _is.SkipToStructEnd()
	if err != nil {
		return err
	}
	_ = have
	return nil
}
`)
}

func (gen *GenGo) genStruct(st *StructInfo) {
	gen.code.Reset()
	gen.vc = 0
	st.rename()

	gen.genHead()
	gen.genStructPackage(st)

	gen.genStructDefine(st)
	gen.genFunResetDefault(st)
	gen.genFunReadFrom(st)
	gen.genFunReadBlock(st)
	gen.genFunWriteTo(st)
	gen.genFunWriteBlock(st)

	gen.saveToSourceFile(st.TName + ".go")
}

func (gen *GenGo) makeEnumName(en *EnumInfo, mb *EnumMember) string {
	return upperFirstLatter(en.TName) + "_" + upperFirstLatter(mb.Key)
}

func (gen *GenGo) genEnum(en *EnumInfo) {
	if len(en.Mb) == 0 {
		return
	}

	gen.code.Reset()
	en.rename()
	gen.genHead()
	gen.genPackage()

	c := &gen.code

	c.WriteString("type " + en.TName + " int32\n")
	c.WriteString("const (\n")
	for _, v := range en.Mb {
		c.WriteString("//" + gen.makeEnumName(en, &v) + " enum\n")
		c.WriteString(gen.makeEnumName(en, &v) + ` = ` + strconv.Itoa(int(v.Value)) + "\n")
	}

	c.WriteString(")\n")

	gen.saveToSourceFile(en.TName + ".go")
}

func (gen *GenGo) genConst(cst []ConstInfo) {
	if len(cst) == 0 {
		return
	}

	gen.code.Reset()
	gen.genHead()
	gen.genPackage()

	c := &gen.code
	c.WriteString("//const as define in tars file\n")
	c.WriteString("const (\n")

	for _, v := range gen.p.Const {
		v.rename()
		c.WriteString(v.Key + " " + gen.genType(v.Type) + " = " + v.Value + "\n")
	}

	c.WriteString(")\n")

	realName := filepath.Base(gen.path)
	ss := strings.Split(realName, ".")
	if len(ss) >= 2 {
		realName = strings.Join(ss[:len(ss)-1], "")
	}
	gen.saveToSourceFile(realName + "_const.go")
}

func (gen *GenGo) genInclude(ps []*Parse) {
	for _, v := range ps {
		gen2 := &GenGo{path: v.Source, prefix: gen.prefix, tarsPath: gTarsPath}
		gen2.p = v
		gen2.genAll()
	}
}

func (gen *GenGo) genAll() {
	if *gAddServant == true {
		gen.p.rename()
	}
	gen.genInclude(gen.p.IncParse)

	for _, v := range gen.p.Enum {
		gen.genEnum(&v)
	}

	gen.genConst(gen.p.Const)

	for _, v := range gen.p.Struct {
		gen.genStruct(&v)
	}

	for _, v := range gen.p.Interface {
		gen.genInterface(&v)
	}
}

func (gen *GenGo) genInterface(itf *InterfaceInfo) {
	gen.code.Reset()
	itf.rename()

	gen.genHead()
	gen.genIFPackage(itf)

	gen.genIFProxy(itf)
	gen.genIFServer(itf)
	gen.genIFServerWithContext(itf)
	gen.genIFDispatch(itf)

	gen.saveToSourceFile(itf.TName + "_IF.go")
}

func (gen *GenGo) genIFProxy(itf *InterfaceInfo) {
	c := &gen.code
	c.WriteString("//" + itf.TName + " struct\n")
	c.WriteString("type " + itf.TName + " struct {" + "\n")
	c.WriteString("s m.Servant" + "\n")
	c.WriteString("}" + "\n")

	for _, v := range itf.Fun {
		gen.genIFProxyFun(itf.TName, &v, false)
		gen.genIFProxyFun(itf.TName, &v, true)

	}

	c.WriteString(`//SetServant sets servant for the service.
func (_obj *` + itf.TName + `) SetServant(s m.Servant) {
	_obj.s = s
}
`)
	c.WriteString(`//TarsSetTimeout sets the timeout for the servant which is in ms.
func (_obj *` + itf.TName + `) TarsSetTimeout(t int) {
	_obj.s.TarsSetTimeout(t)
}
`)
	c.WriteString(`func (_obj *` + itf.TName + `) setMap(l int, res *requestf.ResponsePacket,  ctx map[string]string, sts map[string]string) {
		if l == 1{
			for k, _ := range(ctx){
				delete(ctx, k)
			}
			for k, v := range(res.Context){
				ctx[k] = v
			}
		}else if l == 2 {
			for k, _ := range(ctx){
				delete(ctx, k)
			}
			for k, v := range(res.Context){
				ctx[k] = v
			}
			for k, _ := range(sts){
				delete(sts, k)
			}
			for k, v := range(res.Status){
				sts[k] = v
			}
		}
		}
	`)

	if *gAddServant {
		c.WriteString(`//AddServant adds servant  for the service.
func (_obj *` + itf.TName + `) AddServant(imp _imp` + itf.TName + `, obj string) {
  tars.AddServant(_obj, imp, obj)
}
`)
		c.WriteString(`//AddServant adds servant  for the service with context.
func (_obj *` + itf.TName + `) AddServantWithContext(imp _imp` + itf.TName + `WithContext, obj string) {
  tars.AddServantWithContext(_obj, imp, obj)
}
`)
	}
}

func (gen *GenGo) genIFProxyFun(interfName string, fun *FunInfo, withContext bool) {
	c := &gen.code
	if withContext == true {
		c.WriteString("//" + fun.Name + "WithContext is the proxy function for the method defined in the tars file, with the context\n")
		c.WriteString("func (_obj *" + interfName + ") " + fun.Name + "WithContext(ctx context.Context,")
	} else {
		c.WriteString("//" + fun.Name + " is the proxy function for the method defined in the tars file, with the context\n")
		c.WriteString("func (_obj *" + interfName + ") " + fun.Name + "(")
	}
	for _, v := range fun.Args {
		gen.genArgs(&v)
	}

	c.WriteString(" _opt ...map[string]string)")
	if fun.HasRet {
		c.WriteString("(ret " + gen.genType(fun.RetType) + ", err error){" + "\n")
	} else {
		c.WriteString("(err error)" + "{" + "\n")
	}

	c.WriteString(`
	var length int32
	var have bool
	var ty byte
  `)
	c.WriteString("_os := codec.NewBuffer()")
	var isOut bool
	for k, v := range fun.Args {
		if v.IsOut {
			isOut = true
		} else {
			dummy := &StructMember{}
			dummy.Type = v.Type
			dummy.Key = v.Name
			dummy.Tag = int32(k + 1)
			gen.genWriteVar(dummy, "", fun.HasRet)
		}
	}
	// empty args and below separate
	c.WriteString("\n")
	errStr := errString(fun.HasRet)

	if withContext == false {
		c.WriteString(`
var _status map[string]string
var _context map[string]string
if len(_opt) == 1{
	_context =_opt[0]
}else if len(_opt) == 2 {
	_context = _opt[0]
	_status = _opt[1]
}
_resp := new(requestf.ResponsePacket)
ctx := context.Background()
err = _obj.s.Tars_invoke(ctx, 0, "` + fun.NameStr + `", _os.ToBytes(), _status, _context, _resp)
` + errStr + `
`)
	} else {
		c.WriteString(`var _status map[string]string
var _context map[string]string
if len(_opt) == 1{
	_context =_opt[0]
}else if len(_opt) == 2 {
	_context = _opt[0]
	_status = _opt[1]
}
_resp := new(requestf.ResponsePacket)
err = _obj.s.Tars_invoke(ctx, 0, "` + fun.NameStr + `", _os.ToBytes(), _status, _context, _resp)
` + errStr + `
`)
	}

	if isOut || fun.HasRet {
		c.WriteString("_is := codec.NewReader(tools.Int8ToByte(_resp.SBuffer))")
	}
	if fun.HasRet {
		dummy := &StructMember{}
		dummy.Type = fun.RetType
		dummy.Key = "ret"
		dummy.Tag = 0
		dummy.Require = true
		gen.genReadVar(dummy, "", fun.HasRet)
	}

	for k, v := range fun.Args {
		if v.IsOut {
			dummy := &StructMember{}
			dummy.Type = v.Type
			dummy.Key = "(*" + v.Name + ")"
			dummy.Tag = int32(k + 1)
			dummy.Require = true
			gen.genReadVar(dummy, "", fun.HasRet)
		}
	}

	c.WriteString(`
	_obj.setMap(len(_opt), _resp,_context,_status )
  _ = length
  _ = have
  _ = ty
  `)

	if fun.HasRet {
		c.WriteString("return ret, nil" + "\n")
	} else {
		c.WriteString("return nil" + "\n")
	}

	c.WriteString("}" + "\n")
}

func (gen *GenGo) genArgs(arg *ArgInfo) {
	c := &gen.code
	c.WriteString(arg.Name + " ")
	if arg.IsOut || arg.Type.CType == tkStruct {
		c.WriteString("*")
	}

	c.WriteString(gen.genType(arg.Type) + ",")
}

func (gen *GenGo) genIFServer(itf *InterfaceInfo) {
	c := &gen.code
	c.WriteString("type _imp" + itf.TName + " interface {" + "\n")
	for _, v := range itf.Fun {
		gen.genIFServerFun(&v)
	}
	c.WriteString("}" + "\n")
}

func (gen *GenGo) genIFServerWithContext(itf *InterfaceInfo) {
	c := &gen.code
	c.WriteString("type _imp" + itf.TName + "WithContext interface {" + "\n")
	for _, v := range itf.Fun {
		gen.genIFServerFunWithContext(&v)
	}
	c.WriteString("}" + "\n")
}

func (gen *GenGo) genIFServerFun(fun *FunInfo) {
	c := &gen.code
	c.WriteString(fun.Name + "(")
	for _, v := range fun.Args {
		gen.genArgs(&v)
	}
	c.WriteString(")(")

	if fun.HasRet {
		c.WriteString("ret " + gen.genType(fun.RetType) + ", ")
	}
	c.WriteString("err error)" + "\n")
}

func (gen *GenGo) genIFServerFunWithContext(fun *FunInfo) {
	c := &gen.code
	c.WriteString(fun.Name + "(ctx context.Context, ")
	for _, v := range fun.Args {
		gen.genArgs(&v)
	}
	c.WriteString(")(")

	if fun.HasRet {
		c.WriteString("ret " + gen.genType(fun.RetType) + ", ")
	}
	c.WriteString("err error)" + "\n")
}

func (gen *GenGo) genSwitchCaseBody(tname string, fun *FunInfo) {
	c := &gen.code
	c.WriteString(`func ` + fun.NameStr + `(ctx context.Context, _val interface{},_os *codec.Buffer, _is *codec.Reader, withContext bool)(err error){`)
	c.WriteString(`
	var length int32
	var have bool
	var ty byte
	`)

	for k, v := range fun.Args {
		c.WriteString("var " + v.Name + " " + gen.genType(v.Type))
		if !v.IsOut {
			dummy := &StructMember{}
			dummy.Type = v.Type
			dummy.Key = v.Name
			dummy.Tag = int32(k + 1)
			dummy.Require = true
			gen.genReadVar(dummy, "", false)
		} else {
			c.WriteString("\n")
		}
	}

	if fun.HasRet {
		c.WriteString(`if withContext == false {
		_imp := _val.(_imp` + tname + `)
		ret, err := _imp.` + fun.Name + `(`)
		for _, v := range fun.Args {
			if v.IsOut || v.Type.CType == tkStruct {
				c.WriteString("&" + v.Name + ",")
			} else {
				c.WriteString(v.Name + ",")
			}
		}
		c.WriteString(")")
		c.WriteString(`
		if err != nil {
			return err
		}
		`)
		dummy := &StructMember{}
		dummy.Type = fun.RetType
		dummy.Key = "ret"
		dummy.Tag = 0
		dummy.Require = true
		gen.genWriteVar(dummy, "", false)
		c.WriteString("}else{")
		c.WriteString(`
		_imp := _val.(_imp` + tname + `WithContext)
		ret, err := _imp.` + fun.Name + `(ctx ,`)
		for _, v := range fun.Args {
			if v.IsOut || v.Type.CType == tkStruct {
				c.WriteString("&" + v.Name + ",")
			} else {
				c.WriteString(v.Name + ",")
			}
		}
		c.WriteString(")")
		c.WriteString(`
		if err != nil {
			return err
		}
		`)
		dummy = &StructMember{}
		dummy.Type = fun.RetType
		dummy.Key = "ret"
		dummy.Tag = 0
		dummy.Require = true
		gen.genWriteVar(dummy, "", false)
		c.WriteString("}\n")

	} else {
		c.WriteString(`if withContext == false {
		_imp := _val.(_imp` + tname + `)
		err = _imp.` + fun.Name + `(`)
		for _, v := range fun.Args {
			if v.IsOut || v.Type.CType == tkStruct {
				c.WriteString("&" + v.Name + ",")
			} else {
				c.WriteString(v.Name + ",")
			}
		}
		c.WriteString(")")
		c.WriteString(`
		if err != nil {
			return err
		}
		`)
		c.WriteString("}else{")
		c.WriteString(`
		_imp := _val.(_imp` + tname + `WithContext)
		err = _imp.` + fun.Name + `(ctx ,`)
		for _, v := range fun.Args {
			if v.IsOut || v.Type.CType == tkStruct {
				c.WriteString("&" + v.Name + ",")
			} else {
				c.WriteString(v.Name + ",")
			}
		}
		c.WriteString(")")
		c.WriteString(`
		if err != nil {
			return err
		}
		`)
		c.WriteString("}\n")
	}

	for k, v := range fun.Args {
		if v.IsOut {
			dummy := &StructMember{}
			dummy.Type = v.Type
			dummy.Key = v.Name
			dummy.Tag = int32(k + 1)
			dummy.Require = true
			gen.genWriteVar(dummy, "", false)
		}
	}
	c.WriteString(`
_ = length
_ = have
_ = ty
`)
	c.WriteString(`return nil 
	}`)
	c.WriteString("\n")

}
func (gen *GenGo) genIFDispatch(itf *InterfaceInfo) {
	c := &gen.code
	for _, v := range itf.Fun {
		gen.genSwitchCaseBody(itf.TName, &v)
	}
	c.WriteString("//Dispatch is used to call the server side implemnet for the method defined in the tars file. withContext shows using context or not.  \n")
	c.WriteString("func(_obj *" + itf.TName + `) Dispatch(ctx context.Context, _val interface{}, req *requestf.RequestPacket, resp *requestf.ResponsePacket,withContext bool) (err error) {
  `)

	var param bool
	for _, v := range itf.Fun {
		if len(v.Args) > 0 {
			param = true
			break
		}
	}

	if param {
		c.WriteString("_is := codec.NewReader(tools.Int8ToByte(req.SBuffer))")
	}
	c.WriteString(`
_os := codec.NewBuffer()
switch req.SFuncName {
`)

	for _, v := range itf.Fun {
		gen.genSwitchCase(itf.TName, &v)
	}

	c.WriteString(`
default:
	return fmt.Errorf("func mismatch")
}
var _status map[string]string
s, ok := current.GetResponseStatus(ctx)
if ok  && s != nil {
	_status = s
}
var _context map[string]string
c, ok := current.GetResponseContext(ctx)
if ok && c != nil  {
	_context = c
}
*resp = requestf.ResponsePacket{
	IVersion:     1,
	CPacketType:  0,
	IRequestId:   req.IRequestId,
	IMessageType: 0,
	IRet:         0,
	SBuffer:      tools.ByteToInt8(_os.ToBytes()),
	Status:       _status,
	SResultDesc:  "",
	Context:      _context,
}
return nil
}
`)
}

func (gen *GenGo) genSwitchCase(tname string, fun *FunInfo) {
	c := &gen.code
	c.WriteString(`case "` + fun.NameStr + `":` + "\n")
	c.WriteString(`err := ` + fun.NameStr + `(ctx, _val, _os, _is, withContext)
		if err != nil {
			return err
		}
	`)
}

//Gen to parse file.
func (gen *GenGo) Gen() {
	defer func() {
		if err := recover(); err != nil {
			fmt.Println(err)
			// set exit code
			os.Exit(1)
		}
	}()

	gen.p = ParseFile(gen.path)
	gen.genAll()
}

//NewGenGo build up a new path
func NewGenGo(path string, outdir string) *GenGo {
	if outdir != "" {
		b := []byte(outdir)
		last := b[len(b)-1:]
		if string(last) != "/" {
			outdir += "/"
		}
	}

	return &GenGo{path: path, prefix: outdir}
}
